 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  * Sebastian Davids <sdavids@gmx.de> - 26823 [Markers] cannot reorder columns in task list
  *******************************************************************************/

 package org.eclipse.ui.views.markers.internal;

 import java.lang.reflect.InvocationTargetException ;

 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.NullProgressMonitor;
 import org.eclipse.jface.action.IAction;
 import org.eclipse.jface.action.IMenuListener;
 import org.eclipse.jface.action.IMenuManager;
 import org.eclipse.jface.action.IToolBarManager;
 import org.eclipse.jface.action.MenuManager;
 import org.eclipse.jface.dialogs.IDialogSettings;
 import org.eclipse.jface.operation.IRunnableWithProgress;
 import org.eclipse.jface.viewers.ColumnLayoutData;
 import org.eclipse.jface.viewers.ColumnPixelData;
 import org.eclipse.jface.viewers.IOpenListener;
 import org.eclipse.jface.viewers.ISelectionChangedListener;
 import org.eclipse.jface.viewers.ISelectionProvider;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.jface.viewers.OpenEvent;
 import org.eclipse.jface.viewers.SelectionChangedEvent;
 import org.eclipse.jface.viewers.TableLayout;
 import org.eclipse.jface.viewers.TreeViewer;
 import org.eclipse.jface.viewers.TreeViewerColumn;
 import org.eclipse.jface.viewers.Viewer;
 import org.eclipse.jface.viewers.ViewerComparator;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.BusyIndicator;
 import org.eclipse.swt.events.KeyAdapter;
 import org.eclipse.swt.events.KeyEvent;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Menu;
 import org.eclipse.swt.widgets.ScrollBar;
 import org.eclipse.swt.widgets.Scrollable;
 import org.eclipse.swt.widgets.Tree;
 import org.eclipse.swt.widgets.TreeColumn;
 import org.eclipse.ui.IActionBars;
 import org.eclipse.ui.IMemento;
 import org.eclipse.ui.IViewSite;
 import org.eclipse.ui.PartInitException;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
 import org.eclipse.ui.part.ViewPart;
 import org.eclipse.ui.preferences.ViewPreferencesAction;
 import org.eclipse.ui.progress.IWorkbenchSiteProgressService;

 /**
  * The TableView is a view that generically implements views with tables.
  *
  */
 public abstract class TableView extends ViewPart {

     private static final String TAG_COLUMN_WIDTH = "columnWidth"; //$NON-NLS-1$

     private static final String TAG_COLUMN_ORDER = "columnOrder"; //$NON-NLS-1$

     private static final String TAG_COLUMN_ORDER_INDEX = "columnOrderIndex"; //$NON-NLS-1$

     private static final String TAG_VERTICAL_POSITION = "verticalPosition"; //$NON-NLS-1$

     private static final String TAG_HORIZONTAL_POSITION = "horizontalPosition"; //$NON-NLS-1$

     private TreeViewer viewer;

     private IMemento memento;

     private IAction sortAction;

     private IAction filtersAction;

     private IAction preferencesAction;

     private MarkerTreeContentProvider contentProvider;

     private ISelectionProvider selectionProvider = new MarkerSelectionProviderAdapter();

     /*
      * (non-Javadoc) Method declared on IViewPart.
      */
     public void init(IViewSite site, IMemento memento) throws PartInitException {
         super.init(site, memento);
         this.memento = memento;
     }

     /**
      *
      */
     // void haltTableUpdates() {
 // content.cancelPendingChanges();
 // }
 // void change(Collection toRefresh) {
 // content.change(toRefresh);
 // }
 // void setContents(Collection contents, IProgressMonitor mon) {
 // content.set(contents, mon);
 // }
 abstract protected void viewerSelectionChanged(
             IStructuredSelection selection);

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
      */
     public void createPartControl(Composite parent) {
         parent.setLayout(new FillLayout());

         viewer = new TreeViewer(createTree(parent));
         createColumns(viewer.getTree());
         
         viewer.setComparator(buildComparator());
         setSortIndicators();

         contentProvider = new MarkerTreeContentProvider();

         viewer.setContentProvider(contentProvider);

         viewer.addSelectionChangedListener(new ISelectionChangedListener() {
             public void selectionChanged(SelectionChangedEvent event) {
                 IStructuredSelection selection = (IStructuredSelection) event
                         .getSelection();
                 viewerSelectionChanged(selection);
             }
         });


         // create the actions before the input is set on the viewer but after
 // the
 // sorter and filter are set so the actions will be enabled correctly.
 createActions();

         viewer.setInput(createViewerInput());

         Scrollable scrollable = (Scrollable) viewer.getControl();
         ScrollBar bar = scrollable.getVerticalBar();
         if (bar != null) {
             bar.setSelection(restoreVerticalScrollBarPosition(memento));
         }
         bar = scrollable.getHorizontalBar();
         if (bar != null) {
             bar.setSelection(restoreHorizontalScrollBarPosition(memento));
         }

         MenuManager mgr = initContextMenu();
         Menu menu = mgr.createContextMenu(viewer.getControl());
         viewer.getControl().setMenu(menu);
         getSite().registerContextMenu(mgr, selectionProvider);

         getSite().setSelectionProvider(selectionProvider);

         IActionBars actionBars = getViewSite().getActionBars();
         initMenu(actionBars.getMenuManager());
         initToolBar(actionBars.getToolBarManager());

         registerGlobalActions(getViewSite().getActionBars());

         viewer.addOpenListener(new IOpenListener() {
             public void open(OpenEvent event) {
                 handleOpenEvent(event);
             }
         });
         viewer.getControl().addKeyListener(new KeyAdapter() {
             public void keyPressed(KeyEvent e) {
                 handleKeyPressed(e);
             }
         });
     }


     /**
      * Create the viewer input for the receiver.
      *
      * @return Object
      */
     abstract Object createViewerInput();

     /**
      * Set the comparator to be the new comparator. This should only
      * be called if the viewer has been created.
      *
      * @param comparator
      */
     void setComparator(TableComparator comparator) {
         viewer.setComparator(comparator);
         updateForNewComparator(comparator);
     }

     /**
      * Update the viewer for comparator updates
      *
      * @param comparator
      */
     void updateForNewComparator(TableComparator comparator) {
         comparator.saveState(getDialogSettings());
         viewer.refresh();
         setSortIndicators();
     }

     /**
      * Create the main tree control
      *
      * @param parent
      * @return Tree
      */
     protected Tree createTree(Composite parent) {
         Tree tree = new Tree(parent, SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI
                 | SWT.FULL_SELECTION);
         tree.setLinesVisible(true);
         return tree;
     }

     /**
      * Get the pixel data for the columns.
      *
      * @return ColumnPixelData[]
      */
     public ColumnPixelData[] getSavedColumnData() {
         ColumnPixelData[] defaultData = getDefaultColumnLayouts();

         ColumnPixelData[] result = new ColumnPixelData[defaultData.length];
         for (int i = 0; i < defaultData.length; i++) {
             
             ColumnPixelData defaultPixelData = defaultData[i];
             boolean addTrim = defaultPixelData.addTrim;
             int width = defaultPixelData.width;
             // non-resizable columns are always left at their default width
 if (defaultPixelData.resizable) {
                 if (memento != null) {
                     Integer widthInt = memento.getInteger(TAG_COLUMN_WIDTH + i);

                     if (widthInt != null && widthInt.intValue() > 0) {
                         width = widthInt.intValue();
                         addTrim = false;
                     }
                 }
             }

             result[i] = new ColumnPixelData(width, defaultPixelData.resizable,
                     addTrim);
         }

         return result;
     }

     /**
      * Return the column sizes from the actual widget. Returns the saved column
      * sizes if the widget hasn't been created yet or its columns haven't been
      * initialized yet. (Note that TableLayout only initializes the column
      * widths after the first layout, so it is possible for the widget to exist
      * but have all its columns incorrectly set to zero width - see bug 86329)
      *
      * @return ColumnPixelData
      */
     public ColumnPixelData[] getColumnData() {
         ColumnPixelData[] defaultData = getSavedColumnData();

         Tree tree = getTree();

         if (tree != null && (tree.isDisposed() || tree.getBounds().width == 0)) {
             tree = null;
         }

         TreeColumn[] column = null;
         if (tree != null) {
             column = tree.getColumns();
         }

         ColumnPixelData[] result = new ColumnPixelData[defaultData.length];
         for (int i = 0; i < defaultData.length; i++) {
             ColumnPixelData defaultPixelData = defaultData[i];
             int width = defaultData[i].width;

             if (column != null && i < column.length) {
                 TreeColumn col = column[i];

                 if (col.getWidth() > 0) {
                     width = col.getWidth();
                 }
             }

             result[i] = new ColumnPixelData(width, defaultPixelData.resizable,
                     defaultPixelData.addTrim);
         }

         return result;
     }

     /**
      * Create the columns in the tree.
      *
      * @param tree
      */
     protected void createColumns(final Tree tree) {
         TableLayout layout = new TableLayout();
         tree.setLayout(layout);
         tree.setHeaderVisible(true);

         final IField[] fields = getAllFields();
         ColumnLayoutData[] columnWidths = getSavedColumnData();
         for (int i = 0; i < fields.length; i++) {
             layout.addColumnData(columnWidths[i]);
             TreeColumn tc = new TreeColumn(tree, SWT.NONE, i);
             IField field = fields[i];
             tc.setText(field.getColumnHeaderText());
             tc.setImage(field.getColumnHeaderImage());
             tc.setResizable(columnWidths[i].resizable);
             tc.setMoveable(true);
             tc.addSelectionListener(getHeaderListener());
             tc.setData(field);
             TreeViewerColumn viewerColumn = new TreeViewerColumn(viewer, tc);
             viewerColumn.setLabelProvider(new MarkerViewLabelProvider(field));
         }

         int[] order = restoreColumnOrder(memento);
         if (order != null && order.length == fields.length) {
             tree.setColumnOrder(order);
         }
     }

     /**
      * Create the actions for the receiver.
      */
     protected void createActions() {
         if (getSortDialog() != null) {
             sortAction = new TableSortAction(this, getSortDialog());
         }
     }

     protected MenuManager initContextMenu() {
         MenuManager mgr = new MenuManager();
         mgr.setRemoveAllWhenShown(true);
         mgr.addMenuListener(new IMenuListener() {
             public void menuAboutToShow(IMenuManager mgr) {

                 getViewer().cancelEditing();
                 fillContextMenu(mgr);
             }
         });
         return mgr;
     }

     protected abstract void initToolBar(IToolBarManager tbm);

     /**
      * Init the menu for the receiver.
      *
      * @param menu
      */
     protected void initMenu(IMenuManager menu) {
         if (sortAction != null) {
             menu.add(sortAction);
         }
         addDropDownContributions(menu);
         if (filtersAction != null) {
             menu.add(filtersAction);
         }
         if (preferencesAction != null) {
             menu.add(preferencesAction);
         }
     }

     /**
      * Add any extra contributions to the drop down.
      *
      * @param menu
      */
     void addDropDownContributions(IMenuManager menu) {
         // Do nothing by default.
 }

     protected abstract void registerGlobalActions(IActionBars actionBars);

     protected abstract void fillContextMenu(IMenuManager manager);

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
      */
     public void setFocus() {
         Viewer viewer = getViewer();
         if (viewer != null && !viewer.getControl().isDisposed()) {

             viewer.getControl().setFocus();
         }
     }

     /**
      * Build a comparator from the default settings.
      *
      * @return ViewerComparator
      */
     protected ViewerComparator buildComparator() {

         return createTableComparator();
     }

     /**
      * Create a TableComparator for the receiver.
      *
      * @return TableComparator
      */
     TableComparator createTableComparator() {
         TableComparator sorter = TableComparator
                 .createTableSorter(getSortingFields());
         sorter.restoreState(getDialogSettings());
         return sorter;
     }

     // protected abstract ITableViewContentProvider getContentProvider();

     protected abstract IField[] getSortingFields();

     protected abstract IField[] getAllFields();

     protected abstract IDialogSettings getDialogSettings();

     /**
      * Return the viewer.
      *
      * @return TreeViewer
      */
     protected TreeViewer getViewer() {
         return viewer;
     }

     /**
      * Return the tree for the receiver.
      *
      * @return Tree
      */
     protected Tree getTree() {
         return getViewer().getTree();
     }

     protected SelectionListener getHeaderListener() {
         return new SelectionAdapter() {
             /**
              * Handles the case of user selecting the header area.
              */
             public void widgetSelected(SelectionEvent e) {

                 final TreeColumn column = (TreeColumn) e.widget;
                 final IField field = (IField) column.getData();

                 try {
                     IWorkbenchSiteProgressService progressService = getProgressService();
                     if (progressService == null)
                         BusyIndicator.showWhile(getSite().getShell()
                                 .getDisplay(), new Runnable () {
                             /*
                              * (non-Javadoc)
                              *
                              * @see java.lang.Runnable#run()
                              */
                             public void run() {
                                 resortTable(column, field,
                                         new NullProgressMonitor());

                             }
                         });
                     else
                         getProgressService().busyCursorWhile(
                                 new IRunnableWithProgress() {
                                     /*
                                      * (non-Javadoc)
                                      *
                                      * @see org.eclipse.jface.operation.IRunnableWithProgress#run(org.eclipse.core.runtime.IProgressMonitor)
                                      */
                                     public void run(IProgressMonitor monitor) {
                                         resortTable(column, field, monitor);
                                     }
                                 });
                 } catch (InvocationTargetException e1) {
                     IDEWorkbenchPlugin.getDefault().getLog().log(
                             Util.errorStatus(e1));
                 } catch (InterruptedException e1) {
                     return;
                 }

             }

             /**
              * Resort the table based on field.
              *
              * @param column
              * the column being updated
              * @param field
              * @param monitor
              */
             private void resortTable(final TreeColumn column,
                     final IField field, IProgressMonitor monitor) {
                 TableComparator sorter = getTableSorter();

                 monitor.beginTask(MarkerMessages.sortDialog_title, 100);
                 monitor.worked(10);
                 if (field.equals(sorter.getTopField()))
                     sorter.reverseTopPriority();
                 else
                     sorter.setTopPriority(field);

                 monitor.worked(15);
                 PlatformUI.getWorkbench().getDisplay().asyncExec(
                         new Runnable () {
                             /*
                              * (non-Javadoc)
                              *
                              * @see java.lang.Runnable#run()
                              */
                             public void run() {
                                 viewer.refresh();
                                 updateDirectionIndicator(column);
                             }
                         });

                 monitor.done();
             }
         };
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.views.markers.internal.TableView#getDefaultColumnLayouts()
      */
     protected ColumnPixelData[] getDefaultColumnLayouts() {

         IField[] fields = getAllFields();
         ColumnPixelData[] datas = new ColumnPixelData[fields.length];

         for (int i = 0; i < fields.length; i++) {
             int width = getWidth(fields[i]);
             boolean resizable = width > 0;
             datas[i] = new ColumnPixelData(width, resizable, resizable);
         }
         return datas;
     }

     /**
      * Return the width of the field to display.
      *
      * @param field
      * @return int
      */
     private int getWidth(IField field) {
         if (!field.isShowing()) {
             return 0;
         }
         return field.getPreferredWidth();
     }

     /**
      * Return a sort dialog for the receiver.
      *
      * @return TableSortDialog
      */
     protected TableSortDialog getSortDialog() {
         return new TableSortDialog(getSite(), getTableSorter());

     }

     /**
      * Return the table sorter portion of the sorter.
      *
      * @return TableSorter
      */
     TableComparator getTableSorter() {
         return (TableComparator) viewer.getComparator();
     }

     protected abstract void handleKeyPressed(KeyEvent event);

     protected abstract void handleOpenEvent(OpenEvent event);

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.part.ViewPart#saveState(org.eclipse.ui.IMemento)
      */
     public void saveState(IMemento memento) {
         super.saveState(memento);

         ColumnPixelData[] data = getColumnData();

         for (int i = 0; i < data.length; i++) {
             ColumnPixelData data2 = data[i];
             memento.putInteger(TAG_COLUMN_WIDTH + i, data2.width);
         }
         // save column order
 Tree tree = getTree();
         int[] columnOrder = tree.getColumnOrder();
         for (int i = 0; i < columnOrder.length; i++) {
             IMemento child = memento.createChild(TAG_COLUMN_ORDER);
             child.putInteger(TAG_COLUMN_ORDER_INDEX, columnOrder[i]);
         }
         // save vertical position
 Scrollable scrollable = (Scrollable) viewer.getControl();
         ScrollBar bar = scrollable.getVerticalBar();
         int position = (bar != null) ? bar.getSelection() : 0;
         memento.putInteger(TAG_VERTICAL_POSITION, position);
         // save horizontal position
 bar = scrollable.getHorizontalBar();
         position = (bar != null) ? bar.getSelection() : 0;
         memento.putInteger(TAG_HORIZONTAL_POSITION, position);
     }

     private int[] restoreColumnOrder(IMemento memento) {
         if (memento == null) {
             return null;
         }
         IMemento children[] = memento.getChildren(TAG_COLUMN_ORDER);
         if (children != null) {
             int n = children.length;
             int[] values = new int[n];
             for (int i = 0; i < n; i++) {
                 Integer val = children[i].getInteger(TAG_COLUMN_ORDER_INDEX);
                 if (val != null) {
                     values[i] = val.intValue();
                 } else {
                     // invalid entry so use default column order
 return null;
                 }
             }
             return values;
         }
         return null;
     }

     private int restoreVerticalScrollBarPosition(IMemento memento) {
         if (memento == null) {
             return 0;
         }
         Integer position = memento.getInteger(TAG_VERTICAL_POSITION);
         return (position == null) ? 0 : position.intValue();
     }

     private int restoreHorizontalScrollBarPosition(IMemento memento) {
         if (memento == null) {
             return 0;
         }
         Integer position = memento.getInteger(TAG_HORIZONTAL_POSITION);
         return (position == null) ? 0 : position.intValue();
     }

     /**
      * Get the IWorkbenchSiteProgressService for the receiver.
      *
      * @return IWorkbenchSiteProgressService or <code>null</code>.
      */
     protected IWorkbenchSiteProgressService getProgressService() {
         IWorkbenchSiteProgressService service = null;
         Object siteService = getSite().getAdapter(
                 IWorkbenchSiteProgressService.class);
         if (siteService != null) {
             service = (IWorkbenchSiteProgressService) siteService;
         }
         return service;
     }

     /**
      * Set the filters action.
      *
      * @param action
      */
     void setFilterAction(FiltersAction action) {
         filtersAction = action;

     }

     /**
      * Return the filter action for the receiver.
      *
      * @return IAction
      */
     IAction getFilterAction() {
         return filtersAction;
     }

     /**
      * Return the preferences action.
      *
      * @return IAction
      */
     IAction getPreferencesAction() {
         return preferencesAction;
     }

     /**
      * Set the preferences action.
      *
      * @param preferencesAction
      */
     void setPreferencesAction(ViewPreferencesAction preferencesAction) {
         this.preferencesAction = preferencesAction;
     }

     /**
      * Get the content provider
      *
      * @return MarkerTreeContentProvider
      */
     MarkerTreeContentProvider getContentProvider() {
         return contentProvider;
     }

     /**
      * Return the input to the viewer.
      *
      * @return Object
      */
     public Object getViewerInput() {
         return getViewer().getInput();
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.views.markers.internal.TableView#setSortIndicators()
      */
     void setSortIndicators() {
         IField top = getTableSorter().getTopField();
         TreeColumn[] columns = getViewer().getTree().getColumns();
         for (int i = 0; i < columns.length; i++) {
             TreeColumn column = columns[i];
             if (column.getData().equals(top)) {
                 updateDirectionIndicator(column);
                 return;
             }
         }
     }

     /**
      * Update the direction indicator as column is now the primary column.
      *
      * @param column
      */
     void updateDirectionIndicator(TreeColumn column) {
         getViewer().getTree().setSortColumn(column);
         if (getTableSorter().getTopPriorityDirection() == TableComparator.ASCENDING)
             getViewer().getTree().setSortDirection(SWT.UP);
         else
             getViewer().getTree().setSortDirection(SWT.DOWN);
     }

     /**
      * Set the selection of the receiver.
      *
      * @param selection
      */
     protected void setSelection(IStructuredSelection selection) {
         selectionProvider.setSelection(selection);
     }
 }

